[AWS CDK] CloudFront FunctionでWebサイトにBasic認証をかける
こんにちは、CX事業本部の若槻です。
PoCやデモなどで使用するWebサイト(CloudFront + S3)で「Cognitoを使うほどでは無いけど何らかの認証は掛けておきたい」という場合はBasic認証という選択肢があります。
このBasic認証をCloudFront + S3という構成で掛けたい場合の実装は、今まではLambda@Edgeを使う場合が多かったですが、最近リリースされた、よりユーザーに近いロケーションでより高速な処理が可能なCloudFront Functionsでも実装が可能です。
今回は、このCloudFront Functionを使用してWebサイトにBasic認証をかける設定をAWS CDKで実装してみました。
CloudFront Functionコード
function handler(event) { var request = event.request; var headers = request.headers; // echo -n user:pass | base64 var authString = "Basic dXNlcjpwYXNz"; if ( typeof headers.authorization === "undefined" || headers.authorization.value !== authString ) { return { statusCode: 401, statusDescription: "Unauthorized", headers: { "www-authenticate": { value: "Basic" } } }; } return request; }
下記の記事のコードがシンプルでしたのでそのまま活用させて頂きました。
CDKコード
CloudFront + S3 + CloudFront Function から成る静的ホスティングなWebサイトです。
import * as cdk from "@aws-cdk/core"; import * as cloudfront from "@aws-cdk/aws-cloudfront"; import * as s3 from "@aws-cdk/aws-s3"; import * as s3deploy from "@aws-cdk/aws-s3-deployment"; import * as iam from "@aws-cdk/aws-iam"; export class SampleAppStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const accountId = cdk.Stack.of(this).account; const websiteBucket = new s3.Bucket(this, "WebsiteBucket", { bucketName: `${props.stageName}-${props.projectName}-website-${accountId}`, websiteErrorDocument: "index.html", websiteIndexDocument: "index.html", }); const websiteIdentity = new cloudfront.OriginAccessIdentity( this, "WebsiteIdentity", { comment: `${props.stageName}-${props.projectName}-identity`, } ); const webSiteBucketPolicyStatement = new iam.PolicyStatement({ actions: ["s3:GetObject"], effect: iam.Effect.ALLOW, principals: [ new iam.CanonicalUserPrincipal( websiteIdentity.cloudFrontOriginAccessIdentityS3CanonicalUserId ), ], resources: [`${websiteBucket.bucketArn}/*`], }); websiteBucket.addToResourcePolicy(webSiteBucketPolicyStatement); // CloudFront Functionリソースの定義 const basicAuthFunction = new cloudfront.Function( this, "BasicAuthFunction", { functionName: `basic-authentication`, code: cloudfront.FunctionCode.fromFile({ filePath: "lambda/basic-authentication/index.js", }), } ); const websiteDistribution = new cloudfront.CloudFrontWebDistribution( this, "WebsiteDistribution", { comment: `${props.stageName}-${props.projectName}-distribution`, errorConfigurations: [ { errorCachingMinTtl: 300, errorCode: 403, responseCode: 200, responsePagePath: "/index.html", }, { errorCachingMinTtl: 300, errorCode: 404, responseCode: 200, responsePagePath: "/index.html", }, ], originConfigs: [ { s3OriginSource: { s3BucketSource: websiteBucket, originAccessIdentity: websiteIdentity, }, behaviors: [ { isDefaultBehavior: true, // CloudFront FunctionをDistributionに設定 functionAssociations: [ { eventType: cloudfront.FunctionEventType.VIEWER_REQUEST, function: basicAuthFunction, }, ], }, ], }, ], priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL, } ); new s3deploy.BucketDeployment(this, "WebsiteDeploy", { sources: [s3deploy.Source.asset("./web/build")], destinationBucket: websiteBucket, distribution: websiteDistribution, distributionPaths: ["/*"], }); } }
DistributionへのFunctionの紐付けは、Lambda@Edgeの場合はlambdaFunctionAssociations
を使用しますが、CloudFront Functionの場合はfunctionAssociations
を使用することに注意してください。
動作確認
cdk deploy
でリソースをデプロイします。
WebサイトのURLにアクセスするとブラウザのBasic認証のダイアログが表示されます。
IDとパスワードを入力してログインをクリックします。
S3バケットに静的ホスティングされたWebサイトにアクセスできました。
その他動作としては、IDまたはパスワードを間違えると再度ダイアログが表示され、ダイアログでキャンセルをクリックすると真っ白なページのみ表示される動作となります。
CloudFront Functionでは「Buffer」が使えない?
CloudFront Functionのコード内でBuffer
を使用してIDとパスワードのBase64エンコード出来ないか試してみました。
//var authString = "Basic dXNlcjpwYXNz"; var authString = 'Basic ' + Buffer.from('user:pass').toString('base64');
Functionをデプロイしてテストすると下記のようにBuffer
がReferenceErrorとなりました。
Error Message: The CloudFront function associated with the CloudFront distribution is invalid or could not run. ReferenceError: "Buffer" is not defined in 7
Lambda@Edgeの場合はBuffer
が使えるはずなのですがCloudFront Functionでは使えないようです。高速化の一環としてFunctionへのモジュールのバンドルを最低限にしているようですね。
おわりに
CloudFront Functionを使用してWebサイトにBasic認証をかける設定をAWS CDKで実装してみました。Cognitoの導入などの重い実装もなくサクッとWebサイトに認証機能を導入出来てよかったです。
参考
- CloudFront Function | @aws-cdk/aws-cloudfront module · AWS CDK
- AWS CDK で CloudFront Functions をやってみた | grgr-dkrkのブログ
- CloudFront FunctionsをAWS CDKとCircleCIを用いてE2Eテストおよび自動デプロイに対応する | 超高速 WordPress AMI AMIMOTO
以上